package com.peas.platform.module.icims.base.sys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.peas.platform.common.core.component.FutureTaskCall;
import com.peas.platform.common.core.utils.ValidatorUtil;
import com.peas.platform.framework.mybatisplus.domain.BaseDomain;
import com.peas.platform.framework.mybatisplus.po.BasePo;
import com.peas.platform.framework.mybatisplus.service.BaseServiceImpl;
import com.peas.platform.module.icims.base.sys.adapter.repository.RecycleRepository;
import com.peas.platform.module.icims.base.sys.adapter.repository.po.RecyclePo;
import com.peas.platform.module.icims.base.sys.domain.RecycleDomain;
import com.peas.platform.module.icims.base.sys.dto.RecycleDto;
import com.peas.platform.module.icims.base.sys.service.RecycleService;
import lombok.SneakyThrows;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;

@Service
public class RecycleServiceImpl extends BaseServiceImpl<RecycleDto> implements RecycleService {

    private final Log log = LogFactory.getLog(getClass());

    @Resource
    private SqlSessionFactory sqlSessionFactory;

    @Resource
    private RecycleDomain recycleDomain;

    @Override
    public BaseDomain<RecycleDto, ? extends BasePo> getDomain() {
        return recycleDomain;
    }

    private List<RecyclePo> toPo(List<RecycleDto> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<RecyclePo> ps = new ArrayList<>();
        for (RecycleDto dto : list) {
            if (dto != null) {
                dto.setId(null);
                ps.add(BeanUtil.copyProperties(dto, RecyclePo.class));
            }
        }
        if (CollUtil.isEmpty(ps)) {
            return Collections.emptyList();
        }
        ValidatorUtil.validateList(ps);
        return ps;
    }

    @Transactional
    @Override
    public void saveBatch1(List<RecycleDto> list) {
        List<RecyclePo> ps = toPo(list);
        String sqlStatement = SqlHelper.getSqlStatement(RecycleRepository.class, SqlMethod.INSERT_ONE);
        SqlHelper.executeBatch(sqlSessionFactory, this.log, ps, BaseDomain.default_batch_size, (sqlSession, po) -> sqlSession.insert(sqlStatement, po));
    }

    @Transactional
    @Override
    public void saveBatch2(List<RecycleDto> list) {
        List<RecyclePo> ps = toPo(list);
        recycleDomain.getMapper().customSaveBatch(ps);
    }

    @Transactional
    @Override
    public void saveBatchAsync1(List<RecycleDto> list, int splitSize) {
        async(list, "insert", splitSize);
    }

    @Transactional
    @Override
    public void updateBatch1(List<RecycleDto> list) {
        List<RecyclePo> ps = toPo(list);
        String sqlStatement = SqlHelper.getSqlStatement(RecycleRepository.class, SqlMethod.INSERT_ONE);
        SqlHelper.executeBatch(sqlSessionFactory, this.log, ps, BaseDomain.default_batch_size, (sqlSession, po) -> sqlSession.update(sqlStatement, po));
    }

    @Transactional
    @Override
    public void updateBatch2(List<RecycleDto> list) {
        List<RecyclePo> ps = toPo(list);
        recycleDomain.getMapper().customUpdateBatchById(ps);
    }

    @Transactional
    @Override
    public void updateBatchAsync1(List<RecycleDto> list, int splitSize) {
        async(list, "update", splitSize);
    }

    @SneakyThrows
    private void async(List<RecycleDto> list, String oper, int size) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        Assert.isTrue(list.size() <= BaseDomain.default_batch_size, "批量操作数量不能超过" + BaseDomain.default_batch_size + "条。");
        List<List<RecycleDto>> splits = CollUtil.split(list, size);
        final CountDownLatch countDownLatch = new CountDownLatch(splits.size());
        final ConcurrentLinkedQueue<Long> times = new ConcurrentLinkedQueue<>();
        splits.parallelStream().forEach(dtos -> new FutureTaskCall<List<RecycleDto>, List<Boolean>>().submit(new Function<List<RecycleDto>, List<Boolean>>() {
            @Transactional(propagation = Propagation.REQUIRES_NEW)
            @Override
            public List<Boolean> apply(List<RecycleDto> dtos) {
                try {
                    StopWatch sw = new StopWatch();
                    sw.start();
                    switch (oper.toLowerCase()) {
                        case "insert":
                            saveBatch(dtos);
                            break;
                        case "update":
                            updateBatchById(dtos);
                            break;
                        case "delete":
                            removeBatchByList(dtos);
                            break;
                    }
                    sw.stop();
                    times.add(sw.getLastTaskTimeMillis());
                    List<Boolean> r = new ArrayList<>(dtos.size());
                    for (int i = 0; i < dtos.size(); i++) {
                        r.add(true);
                    }
                    return r;
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    return Collections.emptyList();
                } finally {
                    countDownLatch.countDown();
                }
            }
        }, dtos));
        countDownLatch.await();
        long sum = 0;
        long max = 0;
        for (Long time : times) {
            sum += time;
            max = Math.max(max, time);
        }
        System.out.println("sum:" + sum + ",max:" + max + ",times:" + times);
    }

}
